﻿using System;
using System.Linq;
using System.Windows.Controls;
using NSubstitute;
using Phoenix.Frames;
using Phoenix.Framework;
using Phoenix.Lifetime;
using Phoenix.Tests.Navigation;
using Xunit;

namespace Phoenix.Tests.Frames
{
    public class ContentFrameControllerFacts
    {
        readonly IContentFrameControl contentFrameControl;
        readonly ContentFrameController subject;

        public ContentFrameControllerFacts()
        {
            contentFrameControl = Substitute.For<IContentFrameControl>();
            subject = new ContentFrameController(contentFrameControl);
        }

        [Fact]
        public void register_child_is_ignored_when_there_is_no_current_request()
        {
            // arrange
            var childFrame = Substitute.For<IContentFrame>();
            subject.RegisterChild(childFrame);

            // act/assert
            Assert.Equal(0, subject.Children.Count());
        }

        [Fact]
        public void register_child_is_ignored_if_registering_self()
        {
            // arrange
            var controllerAction = new ControllerAction("", "", null);
            var navigationRequest = new NavigationRequest(controllerAction, null, new UserControl(), new RequestTransientStore(), InvokeOptions.None);
            subject.NavigateTo(navigationRequest);
            subject.RegisterChild(contentFrameControl);

            // act/assert
            Assert.Equal(0, subject.Children.Count());
        }


        [Fact]
        public void child_frames_are_disposed_on_frame_disposal()
        {
            // arrange
            var childFrame = Substitute.For<IContentFrame>();
            var viewModel = Substitute.For<ViewModelBase>();
            var view = new UserControl { DataContext = viewModel };
            var controllerAction = new ControllerAction("", "", null);
            subject.NavigateTo(new NavigationRequest(controllerAction, null, view, new RequestTransientStore(), InvokeOptions.None));
            subject.RegisterChild(childFrame);

            // act
            subject.Dispose();

            // assert
            childFrame.Received().Dispose();
        }

        [Fact]
        public void deactivates_current_request_on_disposal()
        {
            // arrange
            var viewModel = Substitute.For<ViewModelBase>();
            subject.Navigate(viewModel);

            // act
            subject.Dispose();

            // assert
            viewModel.ReceivedWithAnyArgs().Deactivated(null, null);
        }

        [Fact]
        public void child_frames_can_be_collected_once_frame_disposed()
        {
            // arrange
            var viewModel = Substitute.For<ViewModelBase>();
            subject.Navigate(viewModel);
            var childContentFrameReference = CreateAndRegisterChildFrame();

            subject.Dispose();

            // act
            GC.Collect();

            // assert
            Assert.False(childContentFrameReference.IsAlive);
        }

        WeakReference CreateAndRegisterChildFrame()
        {
            var localContentFrame = new TestContentFrame();
            var childContentFrameReference = new WeakReference(localContentFrame);
            subject.RegisterChild(localContentFrame);
            return childContentFrameReference;
        }

        [Fact]
        public void frames_notified_before_disposal_to_give_chance_to_cancel()
        {
            // arrange
            var viewModel = new CancelOnDisposingViewModel();
            subject.Navigate(viewModel);

            // act
            subject.Navigate(new TestViewModel());

            // assert
            Assert.Equal(viewModel, subject.CurrentRequest.View.DataContext);
        }

        [Fact]
        public void deactivates_child_content_frames_on_disposal()
        {
            // arrange
            var childFrame = Substitute.For<IContentFrame>();
            var viewModel = Substitute.For<ViewModelBase>();
            var view = new UserControl { DataContext = viewModel };
            var controllerAction = new ControllerAction("", "", null);
            subject.NavigateTo(new NavigationRequest(controllerAction, null, view, new RequestTransientStore(), InvokeOptions.None));
            subject.RegisterChild(childFrame);

            // act
            subject.DeactivateCurrentRequest();

            // assert
            childFrame.Received().DeactivateCurrentRequest();
        }


        [Fact]
        public void navigate_to_deactivates_current_request_viewmodel()
        {
            // arrange
            var viewModel = Substitute.For<ViewModelBase>();
            subject.Navigate(viewModel);

            // act
            var controllerAction = new ControllerAction("", "", null);
            subject.NavigateTo(new NavigationRequest(controllerAction, null, new UserControl(), new RequestTransientStore(), InvokeOptions.None));

            // assert
            viewModel.ReceivedWithAnyArgs().Deactivated(null, null);
        }

        [Fact]
        public void navigate_to_deactivates_current_request_of_children_in_current_view()
        {
            // arrange
            var viewModel = Substitute.For<ViewModelBase>();
            subject.Navigate(viewModel);
            var childViewModel = Substitute.For<ViewModelBase>();
            var childController = new ContentFrameController(Substitute.For<IContentFrameControl>());
            childController.Navigate(childViewModel);
            subject.RegisterChild(childController);

            // act
            var controllerAction = new ControllerAction("", "", null);
            subject.NavigateTo(new NavigationRequest(controllerAction, null, new UserControl(), new RequestTransientStore(), InvokeOptions.None));

            // assert
            childViewModel.ReceivedWithAnyArgs().Deactivated(null, null);
        }
    }
}